--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
scripts/patch_lxst_codec2_optional.py ec74a2651e897325b14269ae52d11b1e12f0b6e2 (ec74a265) Text, 5.79 KB
T8b949e#!/usr/bin/env python3
T8b949e# SPDX-License-Identifier: 0BSD
T8b949e"""Make LXST Codec2 import optional so missing libcodec2 does not break telephony.
Upstream LXST 0.5.x imports Codec2 unconditionally in Codecs/__init__.py.
When pycodec2/libcodec2 is missing, that raises and prevents Opus profiles
and the whole Telephone stack from loading. Android wheels already soft-import
Codec2. This patch applies the same guard to a site-packages install.
Idempotent. Safe to run after every uv sync / pip install.
"""
Tff7b72from T7ee787__future__ Tff7b72import Te6edf3annotations
Tff7b72import T7ee787importlibT7ee787.T7ee787metadata
Tff7b72import T7ee787sys
Tff7b72from T7ee787pathlib Tff7b72import Te6edf3Path
Te6edf3_CODECS_MARKER Tff7b72= Ta5d6ff"Ta5d6ff# meshchatx-lxst-codec2-optional (do not remove)Tffea00\nTa5d6ff"
Te6edf3_CODECS_INIT Tff7b72= Ta5d6ff"""Ta5d6ff# Copyright 2024-2026, Mark Qvist
Ta5d6ff# meshchatx-lxst-codec2-optional (do not remove)
Ta5d6fffrom .Codec import CodecError as CodecError
Ta5d6fffrom .Codec import Codec as Codec
Ta5d6fffrom .Codec import Null as Null
Ta5d6fffrom .Raw import Raw as Raw
Ta5d6fffrom .Opus import Opus as Opus
Ta5d6ff_CODEC2_IMPORT_ERROR = None
Ta5d6fftry:
Ta5d6ff from .Codec2 import Codec2 as Codec2
Ta5d6ffexcept Exception as _codec2_exc:
Ta5d6ff Codec2 = None
Ta5d6ff _CODEC2_IMPORT_ERROR = _codec2_exc
Ta5d6ffNULL = 0xFF
Ta5d6ffRAW = 0x00
Ta5d6ffOPUS = 0x01
Ta5d6ffCODEC2 = 0x02
Ta5d6ffdef _raise_codec2_unavailable():
Ta5d6ff if _CODEC2_IMPORT_ERROR is not None:
Ta5d6ff raise CodecError(fTa5d6ff"Ta5d6ffCodec2 backend unavailable: Tffd700{_CODEC2_IMPORT_ERROR}Ta5d6ff"Ta5d6ff)
Ta5d6ff raise CodecError(Ta5d6ff"Ta5d6ffCodec2 backend unavailableTa5d6ff"Ta5d6ff)
Ta5d6ffdef codec_header_byte(codec):
Ta5d6ff if codec == Raw:
Ta5d6ff return RAW.to_bytes()
Ta5d6ff elif codec == Opus:
Ta5d6ff return OPUS.to_bytes()
Ta5d6ff elif Codec2 is not None and codec == Codec2:
Ta5d6ff return CODEC2.to_bytes()
Ta5d6ff raise TypeError(fTa5d6ff"Ta5d6ffNo header mapping for codec type Tffd700{codec}Ta5d6ff"Ta5d6ff)
Ta5d6ffdef codec_type(header_byte):
Ta5d6ff if header_byte == RAW:
Ta5d6ff return Raw
Ta5d6ff elif header_byte == OPUS:
Ta5d6ff return Opus
Ta5d6ff elif header_byte == CODEC2:
Ta5d6ff if Codec2 is None:
Ta5d6ff _raise_codec2_unavailable()
Ta5d6ff return Codec2
Ta5d6ff"""
Te6edf3_OLD_GET_CODEC Tff7b72= Ta5d6ff"""Ta5d6ff @staticmethod
Ta5d6ff def get_codec(profile):
Ta5d6ff if profile == Profiles.BANDWIDTH_ULTRA_LOW: return Codec2(mode=Codec2.CODEC2_700C)
Ta5d6ff elif profile == Profiles.BANDWIDTH_VERY_LOW: return Codec2(mode=Codec2.CODEC2_1600)
Ta5d6ff elif profile == Profiles.BANDWIDTH_LOW: return Codec2(mode=Codec2.CODEC2_3200)
Ta5d6ff elif profile == Profiles.QUALITY_MEDIUM: return Opus(profile=Opus.PROFILE_VOICE_MEDIUM)
Ta5d6ff elif profile == Profiles.QUALITY_HIGH: return Opus(profile=Opus.PROFILE_VOICE_HIGH)
Ta5d6ff elif profile == Profiles.QUALITY_MAX: return Opus(profile=Opus.PROFILE_VOICE_MAX)
Ta5d6ff elif profile == Profiles.LATENCY_LOW: return Opus(profile=Opus.PROFILE_VOICE_MEDIUM)
Ta5d6ff elif profile == Profiles.LATENCY_ULTRA_LOW: return Opus(profile=Opus.PROFILE_VOICE_MEDIUM)
Ta5d6ff else: return Opus(profile=Opus.PROFILE_VOICE_MEDIUM)
Ta5d6ff"""
Te6edf3_NEW_GET_CODEC Tff7b72= Ta5d6ff"""Ta5d6ff @staticmethod
Ta5d6ff def get_codec(profile):
Ta5d6ff if Codec2 is not None:
Ta5d6ff if profile == Profiles.BANDWIDTH_ULTRA_LOW: return Codec2(mode=Codec2.CODEC2_700C)
Ta5d6ff elif profile == Profiles.BANDWIDTH_VERY_LOW: return Codec2(mode=Codec2.CODEC2_1600)
Ta5d6ff elif profile == Profiles.BANDWIDTH_LOW: return Codec2(mode=Codec2.CODEC2_3200)
Ta5d6ff if profile == Profiles.QUALITY_MEDIUM: return Opus(profile=Opus.PROFILE_VOICE_MEDIUM)
Ta5d6ff elif profile == Profiles.QUALITY_HIGH: return Opus(profile=Opus.PROFILE_VOICE_HIGH)
Ta5d6ff elif profile == Profiles.QUALITY_MAX: return Opus(profile=Opus.PROFILE_VOICE_MAX)
Ta5d6ff elif profile == Profiles.LATENCY_LOW: return Opus(profile=Opus.PROFILE_VOICE_MEDIUM)
Ta5d6ff elif profile == Profiles.LATENCY_ULTRA_LOW: return Opus(profile=Opus.PROFILE_VOICE_MEDIUM)
Ta5d6ff else: return Opus(profile=Opus.PROFILE_VOICE_MEDIUM)
Ta5d6ff"""
Tff7b72def Td2a8ff_lxst_fileTb4b4b4(Tff7b72*Te6edf3partsTb4b4b4: Tffa657strTb4b4b4) Tff7b72-Tff7b72> Te6edf3Path Tff7b72| Tff7b72NoneTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3dist Tff7b72= Te6edf3importlibTff7b72.Td2a8ffmetadataTff7b72.Td2a8ffdistributionTb4b4b4(Ta5d6ff"Ta5d6fflxstTa5d6ff"Tb4b4b4)
Tff7b72except Te6edf3importlibTff7b72.Td2a8ffmetadataTff7b72.Td2a8ffPackageNotFoundErrorTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72for Te6edf3rel Tff7b72in Te6edf3distTff7b72.Td2a8fffiles Tff7b72or Tb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72if Tffa657tupleTb4b4b4(Te6edf3relTff7b72.Td2a8ffpartsTb4b4b4[Tff7b72-Tffa657lenTb4b4b4(Te6edf3partsTb4b4b4) Tb4b4b4:Tb4b4b4]Tb4b4b4) Tff7b72== Te6edf3partsTb4b4b4:
Tff7b72return Te6edf3PathTb4b4b4(Te6edf3distTff7b72.Td2a8fflocate_fileTb4b4b4(Te6edf3relTb4b4b4)Tb4b4b4)
Tff7b72return Tff7b72None
Tff7b72def Td2a8ffmainTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657intTb4b4b4:
Te6edf3codecs_init Tff7b72= Te6edf3_lxst_fileTb4b4b4(Ta5d6ff"Ta5d6ffCodecsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff__init__.pyTa5d6ff"Tb4b4b4)
Te6edf3telephony Tff7b72= Te6edf3_lxst_fileTb4b4b4(Ta5d6ff"Ta5d6ffPrimitivesTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTelephony.pyTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3codecs_init Tff7b72is Tff7b72NoneTb4b4b4:
Tffa657printTb4b4b4(
Ta5d6ff"Ta5d6ffpatch_lxst_codec2_optional: lxst not installed, nothing to doTa5d6ff"Tb4b4b4,
Te6edf3fileTff7b72=Te6edf3sysTff7b72.Td2a8ffstderrTb4b4b4,
Tb4b4b4)
Tff7b72return T79c0ff0
Te6edf3codecs_text Tff7b72= Te6edf3codecs_initTff7b72.Td2a8ffread_textTb4b4b4(Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3_CODECS_MARKER Tff7b72not Tff7b72in Te6edf3codecs_textTb4b4b4:
Te6edf3codecs_initTff7b72.Td2a8ffwrite_textTb4b4b4(Te6edf3_CODECS_INITTb4b4b4, Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffpatch_lxst_codec2_optional: patched Codecs/__init__.py (Tffd700{Te6edf3codecs_initTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffpatch_lxst_codec2_optional: Codecs already patched (Tffd700{Te6edf3codecs_initTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3telephony Tff7b72is Tff7b72NoneTb4b4b4:
Tffa657printTb4b4b4(
Ta5d6ff"Ta5d6ffpatch_lxst_codec2_optional: Telephony.py not foundTa5d6ff"Tb4b4b4,
Te6edf3fileTff7b72=Te6edf3sysTff7b72.Td2a8ffstderrTb4b4b4,
Tb4b4b4)
Tff7b72return T79c0ff1
Te6edf3tel_text Tff7b72= Te6edf3telephonyTff7b72.Td2a8ffread_textTb4b4b4(Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72if Ta5d6ff"Ta5d6ffif Codec2 is not None:Ta5d6ff" Tff7b72in Te6edf3tel_text Tff7b72and Ta5d6ff"Ta5d6ffBANDWIDTH_ULTRA_LOWTa5d6ff" Tff7b72in Te6edf3tel_textTb4b4b4:
Tffa657printTb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffpatch_lxst_codec2_optional: Telephony get_codec already guarded (Tffd700{Te6edf3telephonyTffd700}Ta5d6ff)Ta5d6ff"
Tb4b4b4)
Tff7b72return T79c0ff0
Tff7b72if Te6edf3_OLD_GET_CODEC Tff7b72not Tff7b72in Te6edf3tel_textTb4b4b4:
Tffa657printTb4b4b4(
Ta5d6ff"Ta5d6ffpatch_lxst_codec2_optional: unexpected Telephony get_codec layout; Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffmanual check required (Tffd700{Te6edf3telephonyTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4,
Te6edf3fileTff7b72=Te6edf3sysTff7b72.Td2a8ffstderrTb4b4b4,
Tb4b4b4)
Tff7b72return T79c0ff1
Te6edf3telephonyTff7b72.Td2a8ffwrite_textTb4b4b4(
Te6edf3tel_textTff7b72.Td2a8ffreplaceTb4b4b4(Te6edf3_OLD_GET_CODECTb4b4b4, Te6edf3_NEW_GET_CODECTb4b4b4, T79c0ff1Tb4b4b4)Tb4b4b4,
Te6edf3encodingTff7b72=Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffpatch_lxst_codec2_optional: patched Telephony get_codec (Tffd700{Te6edf3telephonyTffd700}Ta5d6ff)Ta5d6ff"Tb4b4b4)
Tff7b72return T79c0ff0
Tff7b72if Tff7b72__name__ Tff7b72== Ta5d6ff"Ta5d6ff__main__Ta5d6ff"Tb4b4b4:
Tff7b72raise Tf85149SystemExitTb4b4b4(Te6edf3mainTb4b4b4(Tb4b4b4)Tb4b4b4)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────